import Cesium, { Matrix4 } from '../Ces/Cesium'
import WeFramebuffer from '../Ces/WeFramebuffer'
import Util from '../Core/Util'
import WeDisplay from './WeDisplay'
import WeGeometryUtil from './WeGeometryUtil'
import { optionsToUniforms, WeGridMovableOptions } from './WeGridMovableStyle'
import WeGridMovablePolylineShaders from './WeGridMovablePolylineShaders'
import WeGridMovableUpdatePosition from './WeGridMovableUpdatePosition'
import WeUniform from './WeUniform'

class WeGridMovablePolyline {
  show: boolean = true
  private dirty: boolean = true
  gridMovableUpdatePosition: WeGridMovableUpdatePosition
  weUniform: WeUniform
  private display: WeDisplay
  private drawCommand: any
  private copyCommand: any
  private modelMatrix: Matrix4
  private _freezing: boolean = false
  private currentPosFramebuffer: WeFramebuffer
  private currentLayer: number = -1
  private _initialized: boolean = false
  constructor(options: WeGridMovableOptions, display: WeDisplay) {
    this.weUniform = optionsToUniforms(options)

    const rectangle = display.imageryLayer.rectangle
    const meterSize = Util.computeRectangleMeter(rectangle)
    const meterWidth = meterSize.width
    const meterHeight = meterSize.height
    const useRTCenter = Math.max(meterWidth, meterHeight) < 10000
    const position = new Cesium.Cartesian3()
    if (useRTCenter) {
      Cesium.Cartesian3.fromRadians(
        rectangle.west + rectangle.width * 0.5,
        rectangle.south + rectangle.height * 0.5,
        0.0,
        Cesium.Ellipsoid.default,
        position,
      )
      this.modelMatrix = Util.getEnuModelMatrix(position, new Cesium.Matrix4())
      this.weUniform.Properties.useRTCenter = 1.0
      this.weUniform.Properties.meterWidth = meterWidth
      this.weUniform.Properties.meterHeight = meterHeight
    }

    this.weUniform.Properties.viewExtent = rectangle
    this.display = display
  }

  set viewExtent(value) {
    const rectangle = value
    const meterSize1 = Util.computeRectangleMeter(rectangle)
    const useRTCenter = Math.max(meterSize1.width, meterSize1.height) < 50000
    const dx = this.display.imageryLayer.rectangle.width / this.weUniform.Properties.numLon
    const dy = this.display.imageryLayer.rectangle.height / this.weUniform.Properties.numLat
    if (rectangle.width < dx) {
      rectangle.east += dx * 0.25
      rectangle.west -= dx * 0.25
    }
    if (rectangle.height < dy) {
      rectangle.north += dy * 0.25
      rectangle.south -= dy * 0.25
    }

    const meterSize = Util.computeRectangleMeter(rectangle)
    const meterWidth = meterSize.width
    const meterHeight = meterSize.height
    this.weUniform.Properties.useRTCenter = useRTCenter ? 1.0 : 0.0
    this.weUniform.Properties.meterWidth = meterWidth
    this.weUniform.Properties.meterHeight = meterHeight
    this.weUniform.Properties.viewExtent = rectangle

    if (useRTCenter) {
      const position = new Cesium.Cartesian3()
      Cesium.Cartesian3.fromRadians(
        rectangle.west + rectangle.width * 0.5,
        rectangle.south + rectangle.height * 0.5,
        0.0,
        Cesium.Ellipsoid.default,
        position,
      )
      this.modelMatrix = Util.getEnuModelMatrix(position, new Cesium.Matrix4())
    } else {
      this.modelMatrix = null
    }
  }

  set freezing(value) {
    this._freezing = value
  }

  set showArrow(value) {
    this.weUniform.Properties.particleStyle = value
  }

  rebuild(context) {
    const { display, weUniform } = this
    this.gridMovableUpdatePosition?.destroy()
    this.gridMovableUpdatePosition = new WeGridMovableUpdatePosition({
      display: this.display,
      weUniform,
      context,
    })

    const bufferSize = weUniform.Properties.bufferSize

    const width = weUniform.Properties.numLon
    const height = weUniform.Properties.numLat
    const outputTexture = new Cesium.Texture({
      context,
      width,
      height,
      depth: bufferSize,
      pixelFormat: Cesium.PixelFormat.RGBA,
      pixelDatatype: Cesium.PixelDatatype.FLOAT,
      sampler: Cesium.Sampler.NEAREST,
    })

    this.currentPosFramebuffer = new WeFramebuffer({
      context,
      colorTextures: [outputTexture],
    })

    const { numPointOfLine } = this.weUniform.Properties
    const { vertexArray, attributeLocations } = WeGeometryUtil.createCubeGeometry1(
      context,
      weUniform.Properties.numLon,
      weUniform.Properties.numLat,
      numPointOfLine,
    )

    const uniformMap = {
      u_particlesTexture: () => {
        return this.currentPosFramebuffer.getColorTexture(0)
      },
      currentLayer: () => {
        return this.currentLayer
      },
    }

    weUniform.setupUniformMap(uniformMap)
    display.setupUniformMap(uniformMap)
    if (display.customTextureUniform0) {
      display.customTextureUniform0.setupUniformMap(uniformMap)
    }

    const uniformSource = weUniform.getSource()
    const baseFragmentSource = display.getFragmentShaderSource(true, true, true)

    const vertexShaderSource = `
    #define _MAX_NUM_OF_POINT ${numPointOfLine}\n
    ${uniformSource}
    ${baseFragmentSource}
    ${WeGridMovablePolylineShaders.vertexShaderSource}
    `

    const fragmentShaderSource = `
    ${uniformSource}
    ${display.customTextureUniform0 ? display.customTextureUniform0.getSource() : ''}
    ${baseFragmentSource}
    ${WeGridMovablePolylineShaders.fragmentShaderSource}
    `

    const shaderProgram = Cesium.ShaderProgram.fromCache({
      context,
      vertexShaderSource,
      fragmentShaderSource,
      attributeLocations,
    })

    const renderState = Cesium.RenderState.fromCache({
      cull: {
        enabled: true,
        face: Cesium.CullFace.BACK,
      },
      depthTest: {
        enabled: true,
      },
      blending: Cesium.BlendingState.ALPHA_BLEND,
    })

    this.drawCommand = new Cesium.DrawCommand({
      vertexArray,
      shaderProgram,
      uniformMap,
      renderState,
      pass: Cesium.Pass.TRANSLUCENT,
      modelMatrix: this.modelMatrix,
      // primitiveType: Cesium.PrimitiveType.POINTS
      // primitiveType: Cesium.PrimitiveType.LINES
    })

    const fragmentCopy = `
    uniform sampler2D source;
    in vec2 v_textureCoordinates;
    void main(void){
        out_FragColor = texture(source, v_textureCoordinates);
    }
    `

    this.copyCommand = new Cesium.ComputeCommand({
      shaderProgram: Cesium.ShaderProgram.fromCache({
        context,
        vertexShaderSource: Cesium._shadersViewportQuadVS,
        fragmentShaderSource: `
          ${fragmentCopy}
          `,
      }),
      uniformMap: {
        source: () => this.gridMovableUpdatePosition.getOutputTexture(),
      },
      persists: true,
      owner: this,
    })
    this.copyCommand.framebuffer = this.currentPosFramebuffer
  }

  update(frameState) {
    if (!this.show || !frameState.passes.render) return
    if (this.dirty) {
      this.dirty = false
      this.rebuild(frameState.context)
    }

    if (!this._freezing) {
      this.gridMovableUpdatePosition.update(frameState)
      this.currentLayer = (this.currentLayer + 1) % this.weUniform.Properties.bufferSize
      this.currentPosFramebuffer.currentLayer = this.currentLayer
      frameState.commandList.push(this.copyCommand)
      if (!this._initialized && this.currentLayer === 1) {
        this._initialized = true
      }
    }

    if (!this._initialized) return
    this.drawCommand.modelMatrix = this.modelMatrix
    frameState.commandList.push(this.drawCommand)
  }
  destroy() {
    this.drawCommand = this.drawCommand?.shaderProgram.destroy()
    this.copyCommand = this.copyCommand?.shaderProgram.destroy()
    this.gridMovableUpdatePosition = this.gridMovableUpdatePosition?.destroy()
    this.currentPosFramebuffer = this.currentPosFramebuffer?.destroy()

    return Cesium.destroyObject(this)
  }
  isDestroyed() {
    return false
  }
}

export default WeGridMovablePolyline
